package com.dengyafei.collaborative;

import com.dengyafei.bean.PreferEntity;
import com.dengyafei.bean.RelateEntity;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.util.Lists;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * @author 邓亚非
 * 协同过滤算法核心类
 */
@Data
@Slf4j
public class CoreMath {

    public List<Integer> recommend(Integer userId, List<PreferEntity> list) {
        //找到最近邻用户id
        Map<Double, Integer> distances = computeNearestNeighbor(userId, list);
        Integer nearest = distances.values().iterator().next();

        Map<Integer, List<PreferEntity>> userMap = list.stream().collect(Collectors.groupingBy(PreferEntity::getLid));

        //最近邻用户看过电影列表
        List<Integer> neighborItemList = userMap.get(nearest).stream().map(e -> e.getLval()).collect(Collectors.toList());
        //指定用户看过电影列表
        List<Integer> userItemList = userMap.get(userId).stream().map(e -> e.getLval()).collect(Collectors.toList());
        ;

        //找到最近邻看过，但是该用户没看过的电影，计算推荐，放入推荐列表
        List<Integer> recommendList = new ArrayList<>();
        for (Integer item : neighborItemList) {
            if (!userItemList.contains(item)) {
                recommendList.add(item);
            }
        }
        Collections.sort(recommendList);
        return recommendList;
    }

    /**
     * 在给定userId的情况下，计算其他用户和它的相关系数并排序
     *
     * @param userId
     * @param list
     * @return
     */
    private Map<Double, Integer> computeNearestNeighbor(Integer userId, List<PreferEntity> list) {
        Map<Integer, List<PreferEntity>> userMap = list.stream().collect(Collectors.groupingBy(PreferEntity::getLid));
        Map<Double, Integer> distances = new TreeMap<>();
        userMap.forEach((k, v) -> {
            if (!k.equals(userId)) {
                double distance = pearsonDis(v, userMap.get(userId));
                distances.put(distance, k);
            }
        });
        return distances;
    }


    /**
     * 计算两个序列间的相关系数
     *
     * @param xList
     * @param yList
     * @return
     */
    private double pearsonDis(List<PreferEntity> xList, List<PreferEntity> yList) {
        List<Integer> xs = Lists.newArrayList();
        List<Integer> ys = Lists.newArrayList();
        xList.forEach(x -> {
            yList.forEach(y -> {
                if (x.getGid().equals(y.getGid())) {
                    xs.add(x.getLval());
                    ys.add(y.getLval());
                }
            });
        });
        return getRelate(xs, ys);
    }

    /**
     * 方法描述: 皮尔森（pearson）相关系数计算
     *
     * @param xs
     * @param ys
     * @Return {@link Double}
     */
    public static Double getRelate(List<Integer> xs, List<Integer> ys) {
        int n = xs.size();
        double ex = xs.stream().mapToDouble(x -> x).sum();
        double ey = ys.stream().mapToDouble(y -> y).sum();
        double ex2 = xs.stream().mapToDouble(x -> Math.pow(x, 2)).sum();
        double ey2 = ys.stream().mapToDouble(y -> Math.pow(y, 2)).sum();
        double exy = IntStream.range(0, n).mapToDouble(i -> xs.get(i) * ys.get(i)).sum();
        double numerator = exy - ex * ey / n;
        double denominator = Math.sqrt((ex2 - Math.pow(ex, 2) / n) * (ey2 - Math.pow(ey, 2) / n));
        if (denominator == 0) {
            return 0.0;
        }
        return numerator / denominator;
    }
}